<!--
 * @Author: taosong
 * @Date: 2022-10-26 13:27:06
 * @LastEditors: taosong
 * @LastEditTime: 2022-11-08 13:47:15
 * @Description: taosongs
-->
<template>
  <div
    class="relative"
    ref="containerTarget"
    :style="{
      height: containerHeight + 'px'
    }"
  >
    <!-- 数据渲染 -->
    <template v-if="columnWidth && data.length">
      <div
        v-for="(item, index) in data"
        :key="nodeKey ? item[nodekey] : index"
        class="m-waterfall-item absolute duration-300"
        :style="{
          width: columnWidth + 'px',
          left: item._style?.left + 'px',
          top: item._style?.top + 'px'
        }"
      >
        <slot :item="item" :width="columnWidth" :index="index" />
      </div>
    </template>
    <!-- 加载中提示 -->
    <!-- <template v-else>
      <div>加载中</div>
    </template> -->
  </div>
</template>

<script setup>
import { ref } from '@vue/reactivity'
import {
  computed,
  nextTick,
  onMounted,
  onUnmounted,
  watch
} from '@vue/runtime-core'
import {
  getImgElements,
  getAllImg,
  onComplateImgs,
  getMinHeightColumn,
  getMinHeight,
  getMaxHeight
} from './utils'

const props = defineProps({
  // 数据
  data: {
    type: Array,
    reqired: true
  },
  // 唯一标识 key
  nodeKey: {
    type: String
  },
  // 列数
  column: {
    type: Number,
    default: 2
  },
  // 列间距
  columnSpacing: {
    type: Number,
    default: 20
  },
  // 行间距
  rowSpacing: {
    type: Number,
    default: 20
  },
  // 是否进行图片预读取
  picturePreReading: {
    type: Boolean,
    default: true
  }
})

// 容器的总高度-选取列高最高的作为高度
const containerHeight = ref(0)

// 每列的高度 对象 key：所在列 val：列高
const columnHeightObj = ref({})

// 初始化列高对象
const produceColumnHeightObj = () => {
  columnHeightObj.value = {}
  for (let i = 0; i < props.column; i++) {
    columnHeightObj.value[i] = 0
  }
}
// 容器实例
// 容器宽度 总宽度 - 间隙 / 列数(宽度不包含padding margin border)
const containerTarget = ref(null)

// 容器宽度
const containerWidth = ref(0)
// 容器的左边距 (left)
const containertLeft = ref(0)

// 计算容器宽度
const getContainerWidth = () => {
  const { paddingLeft, paddingRight } = getComputedStyle(
    containerTarget.value,
    null
  )
  // 容器左边距
  containertLeft.value = parseFloat(paddingLeft)
  // 容器宽度
  containerWidth.value =
    containerTarget.value.offsetWidth -
    parseFloat(paddingLeft) -
    parseFloat(paddingRight)
}
// 列宽
const columnWidth = ref(0)
// 列间距的合计
const columnSpacingTotal = computed(() => {
  return (props.column - 1) * props.columnSpacing
})
// 计算列宽
const getColumnWidth = () => {
  getContainerWidth()
  columnWidth.value =
    (containerWidth.value - columnSpacingTotal.value) / props.column
}
onMounted(() => {
  // 计算列宽
  getColumnWidth()
})

// items高度集合
let itemHeights = []

// 监听图片加载完成
// 需要图片预加载
const waitImgComplate = () => {
  itemHeights = []
  // 拿到所有的元素
  let itemElements = [...document.getElementsByClassName('m-waterfall-item')]
  // 获取到元素的img标签
  const imgElements = getImgElements(itemElements)
  // 获取所有 img 标签的图片
  const allImgs = getAllImg(imgElements)
  // 等待图片加载完成
  onComplateImgs(allImgs).then(() => {
    // 图片加载完成可以拿到图片的高度
    itemElements.forEach((el) => {
      itemHeights.push(el.offsetHeight)
    })
    // 渲染位置
    useItemLocation()
  })
}
// 不需要图片预加载
const useItemHeight = () => {
  itemHeights = []
  // 拿到所有元素
  let itemElements = [...document.getElementsByClassName('m-waterfall-item')]
  // setTimeout(() => {
  // 计算 item 高度
  itemElements.forEach((el) => {
    // 依据传入数据计算出的 img 高度
    itemHeights.push(el.offsetHeight)
  })
  // 渲染位置
  useItemLocation()
  // })
}
// 渲染位置
const useItemLocation = () => {
  // 循环数据源
  props.data.forEach((item, index) => {
    if (item._style) return
    item._style = {}
    // 需要计算 item 的 left  top
    // 因为瀑布流图片是要将下一张图片放到最短的列中
    // 需要获取到最短的列 然后计算出left值
    item._style.left = getItemLeft()
    item._style.top = getItemTop()
    // 每次在计算好top 和 left之后需要让列自增 增加本次新渲染图片的高度
    increaseingHeight(index)
  })
  // 指定容器的高度
  containerHeight.value = getMaxHeight(columnHeightObj.value)
}
// 在组件销毁的时候，清除所有的_style
onUnmounted(() => {
  props.data.forEach((item) => {
    delete item._style
  })
})
// 返回下一个item的left
const getItemLeft = () => {
  // 拿到最小宽度的列
  const column = getMinHeightColumn(columnHeightObj.value)
  return (
    // 返回 计算出 item left的值
    column * (columnWidth.value + props.columnSpacing) + containertLeft.value
  )
}
// 返回下一个Item的top
const getItemTop = () => {
  return getMinHeight(columnHeightObj.value)
}
// 高度自增
const increaseingHeight = (index) => {
  // 获取最小高度列
  const minHeightColumn = getMinHeightColumn(columnHeightObj.value)
  columnHeightObj.value[minHeightColumn] +=
    itemHeights[index] + props.rowSpacing
}
// 在watch中触发计算
watch(
  () => props.data,
  (newVal) => {
    // 第一次获取数据时，构建高度记录容器
    const resetContainerHeight = newVal.every((item) => !item._style)
    if (resetContainerHeight) {
      // 构建高度记录容器
      produceColumnHeightObj()
    }
    nextTick(() => {
      if (props.picturePreReading) {
        waitImgComplate()
      } else {
        useItemHeight()
      }
    })
  },
  {
    deep: true,
    immediate: true
  }
)
// 重置列宽
const reset = () => {
  setTimeout(() => {
    getColumnWidth()
    props.data.forEach((item) => {
      item._style = null
    })
  }, 200)
}
watch(
  () => props.column,
  () => {
    if (props.picturePreReading) {
      columnWidth.value = 0
      reset()
    } else {
      reset()
    }
  }
)
</script>
<style lang="scss" scoped></style>
